package vertx.starter.refactor;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.core.eventbus.Message;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.jdbc.JDBCClient;
import io.vertx.ext.sql.SQLConnection;
import io.vertx.ext.sql.UpdateResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;

import static vertx.starter.conf.DatabaseConstants.CONFIG_WIKIDB_JDBC_DRIVER_CLASS;
import static vertx.starter.conf.DatabaseConstants.CONFIG_WIKIDB_JDBC_MAX_POOL_SIZE;
import static vertx.starter.conf.DatabaseConstants.CONFIG_WIKIDB_JDBC_URL;
import static vertx.starter.conf.DatabaseConstants.DEFAULT_JDBC_MAX_POOL_SIZE;
import static vertx.starter.conf.DatabaseConstants.DEFAULT_WIKIDB_JDBC_DRIVER_CLASS;
import static vertx.starter.conf.DatabaseConstants.DEFAULT_WIKIDB_JDBC_URL;

public class DatabaseVerticle extends AbstractVerticle {


  public static final String CONFIG_WIKIDB_SQL_QUERIES_RESOURCE_FILE = "wikidb.sqlqueries.resource.file";

  public static final String CONFIG_WIKIDB_QUEUE = "wikidb.queue";

  private static final
  Logger LOGGER = LoggerFactory.getLogger(DatabaseVerticle.class);


  public enum SqlQuery {
    CREATE_PAGES_TABLE,
    ALL_PAGES,
    GET_PAGE,
    CREATE_PAGE,
    SAVE_PAGE,
    DELETE_PAGE,
    DELETE_ALL_PAGE
  }

  private final HashMap<SqlQuery, String> sqlQueries = new HashMap<>();

  private void loadSqlQueries() throws IOException {

    String queriesFile = config().getString(CONFIG_WIKIDB_SQL_QUERIES_RESOURCE_FILE);
    InputStream queriesInputStream;
    if (queriesFile != null) {
      queriesInputStream = new FileInputStream(queriesFile);
    } else {
      queriesInputStream = getClass().getResourceAsStream("/db-queries.properties");
    }

    Properties queriesProps = new Properties();
    queriesProps.load(queriesInputStream);
    queriesInputStream.close();

    sqlQueries.put(SqlQuery.CREATE_PAGES_TABLE, queriesProps.getProperty("create-pages-table"));
    sqlQueries.put(SqlQuery.ALL_PAGES, queriesProps.getProperty("all-pages"));
    sqlQueries.put(SqlQuery.GET_PAGE, queriesProps.getProperty("get-page"));
    sqlQueries.put(SqlQuery.CREATE_PAGE, queriesProps.getProperty("create-page"));
    sqlQueries.put(SqlQuery.SAVE_PAGE, queriesProps.getProperty("save-page"));
    sqlQueries.put(SqlQuery.DELETE_PAGE, queriesProps.getProperty("delete-page"));
    sqlQueries.put(SqlQuery.DELETE_ALL_PAGE, queriesProps.getProperty("delete-all-page"));
  }

  private JDBCClient dbClient;

  @Override
  public void start(Promise<Void> promise) throws Exception {

    /*
     * Note: this uses blocking APIs, but data is small...
     */
    loadSqlQueries();

    JDBCClient dbClient = JDBCClient.createShared(vertx, new JsonObject()
  .put("url", config().getString(CONFIG_WIKIDB_JDBC_URL, DEFAULT_WIKIDB_JDBC_URL))
  .put("driver_class", config().getString(CONFIG_WIKIDB_JDBC_DRIVER_CLASS, DEFAULT_WIKIDB_JDBC_DRIVER_CLASS))
  .put("max_pool_size", config().getInteger(CONFIG_WIKIDB_JDBC_MAX_POOL_SIZE, DEFAULT_JDBC_MAX_POOL_SIZE)));

    dbClient.getConnection(ar -> {
      if (ar.failed()) {
        LOGGER.error("Could not open a database connection", ar.cause());
        promise.fail(ar.cause());
      } else {
        SQLConnection connection = ar.result();
        connection.execute(sqlQueries.get(SqlQuery.CREATE_PAGES_TABLE), create -> {
          connection.close();
          if (create.failed()) {
            LOGGER.error("Database preparation error", create.cause());
            promise.fail(create.cause());
          } else {
            vertx.eventBus().consumer(config().getString(CONFIG_WIKIDB_QUEUE, "wikidb.queue"), this::onMessage);
            promise.complete();

          }
        });
      }
    });
  }

  public void onMessage(Message<JsonObject> message) {

    if (!message.headers().contains("action")) {
      LOGGER.error("No action header specified for message with headers {} and body {}",
        message.headers(), message.body().encodePrettily());
      message.fail(50, "No action header specified");
      return;
    }
    String action = message.headers().get("action");

    switch (action) {
      case "all-pages":
        fetchAllPages(message);
        break;
      case "create-page":
        createPage(message);
        break;
      case "delete-page":
        deletePage(message);
        break;
      default:
        message.fail(400, "Bad action: " + action);
    }
  }

  private void deletePage(Message<JsonObject> message) {
    JsonObject body = message.body();
    String name = body.getString("name");
    JsonArray sqlParma = new JsonArray();
    if (name != null)
      sqlParma.add(name);
    dbClient.getConnection(car -> {
      SQLConnection conn = car.result();
      String sql = name == null ? sqlQueries.get(SqlQuery.DELETE_ALL_PAGE) : sqlQueries.get(SqlQuery.DELETE_PAGE);
      LOGGER.info(sql);
      conn.updateWithParams(sql, sqlParma, ah -> {
        conn.close();
        if (ah.succeeded()) {
          UpdateResult result = ah.result();
          message.reply(result.getUpdated());
        } else {
          message.fail(-1, "失败!");
        }
      });
    });
  }


  private void fetchAllPages(Message<JsonObject> message) {
    dbClient.getConnection(car -> {
      if (car.succeeded()) {
        SQLConnection connection = car.result();
        connection.query(sqlQueries.get(SqlQuery.ALL_PAGES), res -> {
          connection.close();
          if (res.succeeded()) {
            List<JsonArray> pages = res.result()
              .getResults();
            message.reply(new JsonObject().put("pages", new JsonArray(pages)));
          } else {
            reportQueryError(message, res.cause());
          }
        });
      } else {
        reportQueryError(message, car.cause());
      }
    });
  }

  private void createPage(Message<JsonObject> message) {

    JsonObject request = message.body();
    JsonArray data = new JsonArray()
      .add(request.getString("name"))
      .add(request.getString("content"));

    dbClient.updateWithParams(sqlQueries.get(SqlQuery.CREATE_PAGE), data, res -> {
      if (res.succeeded()) {
        message.reply("ok");
      } else {
        reportQueryError(message, res.cause());
      }
    });
  }

  private void reportQueryError(Message<JsonObject> message, Throwable cause) {
    LOGGER.error("Database query error", cause);
    message.fail(-1, cause.getMessage());
  }

}
